package com.tool.grab.vpn;

import android.net.VpnService;
import android.os.Build;
import android.os.ParcelFileDescriptor;

import com.tool.grab.Constant;
import com.tool.grab.Logs;
import com.tool.grab.R;
import com.tool.grab.vpn.header.HttpPacket;
import com.tool.grab.vpn.header.IPPacket;
import com.tool.grab.vpn.header.TCPPacket;
import com.tool.grab.vpn.header.UDPPacket;
import com.tool.grab.vpn.task.TcpService;
import com.tool.grab.vpn.task.TunnelService;
import com.tool.grab.vpn.task.UDPService;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class HttpVPNService extends VpnService implements Runnable{
    private static final String TAG = HttpVPNService.class.getSimpleName();
    private Thread vpnThread;
    private TcpService tcpService;
    private UDPService udpService;
    private TunnelService tunnelService;
    private boolean isRun;

    private long createTimeLong;
    private String createTimeString;

    private ParcelFileDescriptor descriptor;
    private FileInputStream inputStream;
    private FileOutputStream outputStream;

    private  final ConcurrentHashMap<Integer , NetSession> sessions = new ConcurrentHashMap<>();


    @Override
    public void onCreate() {
        super.onCreate();
        Logs.i(TAG,"创建代理");
        VPNManager.Instance().setService(this);
        vpnThread = new Thread(this,"VpnThread");
        vpnThread.start();
    }

    @Override
    public void onDestroy() {
        stop();
    }

    public Builder builder(){
        return new Builder();
    }

    @Override
    public void run() {
        try {
            while (prepare(this) != null){
                Thread.sleep(20);
            }

            tunnelService = new TunnelService(this);
            tunnelService.start();

            establishVPN();

            startStream();

        }catch (Exception e){
           Logs.i(TAG,"VPN异常关闭\n"+e.toString());
        }finally {
            stop();
        }
    }

    /**
     * 根据配置创建VPN代理  获取到文件描述符 建立VPN连接
     * @throws Exception
     */
    private void establishVPN() throws Exception {
        Builder builder = new Builder();
        builder.setMtu(Constant.MUTE_SIZE);
        builder.setSession(getString(R.string.app_name));
        builder.addAddress(Constant.ADDRESS.Address, Constant.ADDRESS.port);
        builder.addRoute(Constant.ROUTE_HOST, Constant.ROUTE_PORT);

        builder.addDnsServer(Constant.GOOGLE_DNS_FIRST);
        builder.addDnsServer(Constant.CHINA_DNS_FIRST);
        builder.addDnsServer(Constant.GOOGLE_DNS_SECOND);
        builder.addDnsServer(Constant.AMERICA);

        createTimeLong = System.currentTimeMillis();
        createTimeString = Constant.timeToString(createTimeLong);

        String target = Constant.getPackageName(this);
        if (target != null && !target.equals("")) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                builder.addAllowedApplication(target);
                builder.addAllowedApplication(getPackageName());
                // 成功添加需要拦截的目标
                isRun = true;
            }
        }
        descriptor = builder.establish();
        if(descriptor == null)throw new Exception("建立VPN连接异常");
    }

    /**
     * 开始读写
     * @throws Exception
     */
    private void startStream() throws Exception {
        outputStream = new FileOutputStream(descriptor.getFileDescriptor());
        inputStream = new FileInputStream(descriptor.getFileDescriptor());
        byte[] bytes = new byte[Constant.MUTE_SIZE];
        int size = 0;

        while (isRun && size != -1) {
            size = inputStream.read(bytes);

            if(size <= 0){
                Thread.sleep(20);
                continue;
            }

            if (!tunnelService.isRun()) throw new Exception("通道服务已经停止.");

            synchronized (tunnelService){
                if(!tunnelService.isRun()){
                    tunnelService.receivePacket(bytes,size);
                }
            }
        }
    }



    private void onReceivedTcpPacket(IPPacket packet)throws IOException{
        IPPacket ipPacket = packet;
        TCPPacket tcpPacket = new TCPPacket(packet.getBytes(),packet.getOffset() + packet.getHeaderLength(),packet.getValidLength());
        tcpPacket.setPacket(ipPacket);
        HttpPacket httpPacket = new HttpPacket(packet.getBytes(),tcpPacket.getOffset() + tcpPacket.getHeaderLength(),packet.getValidLength());
        httpPacket.setPacket(tcpPacket);

        int remoteIP = packet.getRemoteIpInt();
        int remotePort = tcpPacket.getRemotePort();
        int localIP = packet.getLocalIpInt();
        int localPort = tcpPacket.getLocalPort();

        NetSession session;

        if(tcpPacket.getLocalPort() == tcpService.getPort()){
            session = sessions.get(remotePort);
            if(session != null){
                ipPacket.setLocalIp(remoteIP);
                tcpPacket.setLocalPort(remotePort);

                ipPacket.setRemoteIp(session.getRemoteIP());

                tcpPacket.refreshCheckSum();
                ipPacket.refreshCheckSum();
                outputStream.write(ipPacket.getBytes(),0,ipPacket.getValidLength());
            }
        }else{
               session = sessions.get(localPort);

            if(session == null || session.getRemoteIP() != remoteIP || session.getRemotePort() != remotePort){
                session = new NetSession();
                session.setType(ipPacket.getProtocl());
                session.setLocalIP(localIP);
                session.setLocalPort(localPort);
                session.setRemoteIP(remoteIP);
                session.setRemotePort(remotePort);
                sessions.put(localPort,session);
            }

            session.increase();
            session.refreshTime();

            int dataLength = httpPacket.getDataLength();

            if(httpPacket.isHttp() && session.getHost() == null && session.getUrl() == null){
                if(httpPacket.getHttpUrl() != null ){
                    session.setHost(httpPacket.getHttpHost());
                    session.setUrl(httpPacket.getHttpUrl());
                }
            }

            ipPacket.setLocalIp(remoteIP);
            ipPacket.setRemoteIp(Constant.IP_INT);
            tcpPacket.setRemotePort(tcpService.getPort());
            tcpPacket.refreshCheckSum();
            ipPacket.refreshCheckSum();
            outputStream.write(ipPacket.getBytes(),0,ipPacket.getValidLength());
            session.setSendBytes(session.getSendBytes()+ dataLength);
        }
    }

    private void onReceivedUdpPacket(IPPacket packet){
        UDPPacket udpPacket = new UDPPacket(packet.getBytes(),packet.getOffset() + packet.getHeaderLength(),packet.getValidLength());
        udpPacket.setPacket(packet);

        int remoteIP = packet.getRemoteIpInt();
        int remotePort = udpPacket.getRemotePort();
        int localIP = packet.getLocalIpInt();
        int localPort = udpPacket.getLocalPort();

        NetSession session = sessions.get(localPort);
        if(session == null || session.getRemoteIP() != remoteIP || session.getRemotePort() != remotePort){
            session = new NetSession();
            session.setLocalIP(localIP);
            session.setLocalPort(localPort);
            session.setRemoteIP(remoteIP);
            session.setRemotePort(remotePort);
            session.setType(packet.getProtocl());
            sessions.put(localPort,session);
        }
        session.increase();
        session.refreshTime();

        udpService.processPacket(localPort,udpPacket);
    }




    /**
     * 清除过期的会话
     */
    private void clearExpiredSessions() {
        long now = System.currentTimeMillis();
        Set<Map.Entry<Integer, NetSession>> entries = sessions.entrySet();
        Iterator<Map.Entry<Integer, NetSession>> iterator = entries.iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer, NetSession> next = iterator.next();
            if (now - next.getValue().getModificationTime() > 60 * 1000L) {
                iterator.remove();
            }
        }
    }

    private synchronized void stop(){
        isRun = false;

        if(vpnThread != null){
            vpnThread.interrupt();
            vpnThread = null;
        }

        if(inputStream != null){
            try {
                inputStream.close();
            } catch (IOException e) {}finally {
                inputStream = null;
            }
        }

        if(outputStream != null){
            try {
                outputStream.close();
            } catch (IOException e) {}finally {
                outputStream = null;
            }
        }

        if(descriptor != null){
            try {
                descriptor.close();
            } catch (IOException e) {}finally {
                descriptor = null;
            }
        }
    }

    public boolean isRun() {
        return isRun;
    }

}
